home *** CD-ROM | disk | FTP | other *** search
- #include "GameAEvents.h"
- #include "ZAM.h"
- #include "WindowDispatch.h"
- #include "TankSprite.h"
- #include "MissileSprite.h"
- #include "CoreGlobals.h"
- #include "ZAMProtos.h"
-
- #define rNotifySICN 128
- #define rWakeUpSND 128
- #define kDiamondMark 1
- #define rReqGameAlert 130
- #define AcceptBtnItem 1
-
-
-
-
- pascal OSErr AERequestGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AEAcceptGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AERefuseGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AEAnswer (AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon);
- pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
-
- Boolean gByeNeeded;
- long gLastSynchTime;
- long gLocalTime;
- long gLastReturnTime;
-
-
- void InstallCustomEvents(gamePtr game)
- /*
- This installs the apple event handlers used by the game.
-
- */
- {
- OSErr err = noErr;
-
- gByeNeeded = false;
- gLastSynchTime = 0;
- gLastReturnTime = 0;
- gLocalTime = 0;
-
- err = AEInstallEventHandler (kCoreEventClass, kAEAnswer, AEAnswer, (long)game, false);
- if(err) {
- ErrMsgCode("\pCould not install AE handler.",err);
- ExitToShell();
- }
-
- err = AEInstallEventHandler (kZAMEventClass, kRequestGameID, AERequestGame,(long)game, false);
- if(err) {
- ErrMsgCode("\pCould not install AE handler.",err);
- ExitToShell();
- }
-
- err = AEInstallEventHandler (kZAMEventClass, kGoodByeID, AEGoodbye,(long)game, false);
- if(err) {
- ErrMsgCode("\pCould not install AE handler.",err);
- ExitToShell();
- }
-
- err = AEInstallEventHandler (kZAMEventClass, kTankSynchID, AESynchTank, (long)game, false);
- if(err) {
- ErrMsgCode("\pCould not install AE handler.",err);
- ExitToShell();
- }
- }
-
-
- void SendGoodBye(void)
- /*
- When quit is selected it calls this function, which sends a message to the other player,
- letting them know you are no longer around.
-
- It would be nice if the quit handler displayed even a simple dialog,
- but this was a pain for my testing, so I made it just quit. Of course a real
- application would not behave this way.
- */
- {
- AppleEvent goodByeEvt;
- AppleEvent reply;
- OSErr err = noErr;
- Boolean disposeNeeded = false;
-
- #ifdef NO_NET
- return;
- #endif
-
- if(gByeNeeded) {
- err = AECreateAppleEvent(kZAMEventClass, kGoodByeID, &gGame->oppAddr,
- kAnyTransactionID, gGame->gameID, &goodByeEvt);
- if(err != noErr) {
- ErrMsgCode("\p Failure: SendGoodBye AECreateAppleEvent",err);
- }
-
- if(err == noErr) {
- disposeNeeded = true;
- err = AESend(&goodByeEvt, &reply, kAENoReply + kAECanInteract,
- kAEHighPriority, 60 * 60, nil, nil);
- if(err != noErr) {
- ErrMsgCode("\p SendGoodBye: AESend goodByeEvt",err);
- }
- }
-
-
- if(disposeNeeded) {
- AEDisposeDesc(&goodByeEvt);
- }
- }
- }
-
- pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- This is the handler for when the remote mac has quit.
- It does not display any warning dialog box, because as I mentioned before,
- in a code-build-test cycle, it was too much to be navigating dialogs and stuff.
- So, it just causes the program to quit without warning.
- */
- {
- gDone = true;
- gByeNeeded = false;
- }
-
- pascal OSErr AEAnswer(AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- This handler is used for the apple events that return a result asynchronously.
- The type of reply is stored in the keyReturnIDAttr. The only events that require
- a reply are the request game event, and the synch event. Another synch event will
- not be sent until an ack is received.
- */
- {
- long actType,actSize;
- short retID;
- OSErr err= noErr;
-
-
- err = AEGetAttributePtr(theAE, keyReturnIDAttr, typeShortInteger, &actType,
- &retID, sizeof(short), &actSize);
- if(err != noErr) {
- ErrMsgCode("\pAEAnswer: AEGetAttr keyReturnIDAttr",err);
- }
-
- if(err == noErr) {
- switch(retID) {
- case kAcceptID: err = AEAcceptGame(theAE, reply, rfCon);
- break;
-
- case kTimeID: err = AEAckTime(theAE, reply, rfCon);
- break;
-
- default: ErrMsgCode("\pUnknown Return ID.",retID);
- break;
- }
- }
-
- return err;
- }
-
-
- pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- just grab the time so the synch event will not flood the wire with
- unhandled events. This is not a REAL appleevent handler, it just
- looks like one, because it was easier that way. This is actually called
- by the return event handler, above.
- */
- {
- OSErr err = 0;
- long len;
- DescType actualType;
- long synchTime;
-
- err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType,
- &gLastReturnTime, sizeof(long), &len);
-
- if(err != noErr) {
- ErrMsgCode("\p Failed: AEAckTime keySynchTime, typeLongInteger",err);
- }
-
- return err;
- }
-
- pascal OSErr AEAcceptGame(AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- This event comes back when the other player accepts the game. Any ZAM game will
- accept if it is not already playing. I used to have code to allow you to type in
- a name, and request a game, and the other player would be able to decide if they
- wanted to or not. Again, this really hampered my code-compile-test cycle, so I stripped
- or commented it out. You could probably revive it if you wanted to.
-
- This is not a REAL appleevent handler, it just
- looks like one, because it was easier that way. This is actually called
- by the return event handler, above.
- */
- {
- OSErr err = noErr;
- long actualType,longSize;
- Boolean acceptFlag;
-
-
- if(gGame == nil) {
- ErrMsg("\pReceived accept when never made window");
- err = paramErr;
- }
-
- if(gGame->gameState == kWaitingForAccept) {
- err = AEGetParamPtr(theAE, keyAnswer, typeBoolean, &actualType,
- &acceptFlag, sizeof(Boolean), &longSize);
- if(err != noErr) {
- ErrMsgCode("\p Failed: AEAcceptGame AEGetParamPtr typeBoolean",err);
- }
-
- if(err == noErr)
- if(acceptFlag) {
- gGame->gameState = kGameInProgress;
- /* Set up tank indexes here */
- gGame->localTankIndex = 0;
- gGame->remoteTankIndex = 1;
- PlaceTankSprites(gGame);
- gByeNeeded = true;
- } else {
- /* display rejection notice */
- ParamText("\pThat person has refused your challenge. Try another.",nil,nil,nil);
- (void) Alert(131,nil); // evil numbers must go
- gGame->gameState = kWaitingForRequest;
- }
- }
-
- return err;
- }
-
-
- pascal OSErr AERequestGame( AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- This BIG ugly function handles requests for a game from another player
- and starts things rolling. It changes the game state flag, but
- this flag is mostly ignored by the rest of the program. At one time
- I was using it, but I was not setting it properly all the time, and there
- were more important bugs to fix so I just crippled it.
-
-
- */
- {
- OSErr err = noErr;
- short itemHit;
- DescType returnedType;
- Size length;
- long replyID;
- Str255 nameStr;
- AEAddressDesc targetAddress;
- AppleEvent replyEvent;
- Boolean acceptFlag = true;
- ulong tranID;
-
- if(err == noErr) {
- if(gGame->gameState != kWaitingForRequest) {
- /* just automagically refuse the game cause we are busy */
- acceptFlag = false;
- err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean));
- if(err != noErr) {
- ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer FALSE",err);
- }
- } else {
-
- /* get the address of the person requesting we play */
- if(err == noErr) {
- err = AEGetAttributeDesc ( theAE, keyAddressAttr, typeWildCard, &targetAddress);
- if(err != noErr) {
- ErrMsgCode("\p AERequestGame: AEGetAttr keyAddressAttr",err);
- }
- }
-
- if(err == noErr) {
- err = AEGetAttributePtr(theAE, keyTransactionIDAttr, typeLongInteger, &returnedType,
- &tranID, sizeof(long), &length);
- if(err != noErr) {
- ErrMsgCode("\p AEGetAttr keyTransactionIDAttr",err);
- }
- }
-
- if(err == noErr) {
- /* ask user if they want to play */
- /* NOTE: this is where I used to display the dialog asking */
- /* if a game was wanted or not */
- /* now it always accepts */
- acceptFlag = true;
- if(err == noErr) {
- gGame->oppAddr = targetAddress;
- gGame->gameID = tranID;
- gGame->localTankIndex = 1;
- gGame->remoteTankIndex = 0;
- gGame->gameState = kGameInProgress;
- PlaceTankSprites(gGame);
- gByeNeeded = true;
- }
- else if(err == paramErr) {
- acceptFlag = false;
- err = noErr;
- }
-
- if(err == noErr) {
- err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean));
- if(err != noErr) {
- ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer",err);
- }
- }
- }
- }
- }
- return err;
- }
-
-
- pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon)
- /*
- This is the network heartbeat of the game. It recieves the state of the remote machine.
-
- It does some things with AppleEvents for speed
- reasons that a normal application would not want to do. I send large structures
- and arrays, instead of building platform independant AEDescLists and that sort of thing.
- I send the current state of the tank, and the current state of all the missiles (from
- a subroutine call, see the code).
- */
- {
-
- OSErr err = 0;
- long len;
- DescType actualType;
- long synchTime;
- TankStatus tStatus;
-
- /* first thing is to get the synch time to see if this one
- should be ignored. The synch time is saved off each time
- so the network is not flooded with synch events.
- It is very easy to flood a network with these things.
- Sometimes a mac may be busy and miss a few events, and then when it gets back
- to it, it caused a hyper-spurt of animation while the mac caught up
- and processed the apple events. This was undesireable for my sample, so
- I implemented this strategy of synching */
-
- if(err == noErr) {
- /* get the synch time to see if we should ignore this or not*/
- err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType,
- &synchTime, sizeof(long), &len);
- if(err != noErr) {
- ErrMsgCode("\p Failed: AESynchTank keyTankPosition, typefixPt",err);
- }
- }
-
- if(err == noErr) {
- /* send the synch time back */
- err = AEPutParamPtr(reply, keySynchTime, typeLongInteger,
- &synchTime, sizeof(long));
- if(err != noErr) {
- ErrMsgCode("\p Failure: AESynchTank AEPutParamPtr reply",err);
- }
- }
-
- if(err == noErr) {
- if(gLastSynchTime > synchTime)
- err = 1;
- else
- gLastSynchTime = synchTime;
- }
-
- /*
- this gets the status of the tank from the event */
- if(err == noErr) {
- err = AEGetParamPtr(theAE, keyTankStatus, typeTankStatus, &actualType,
- &tStatus, sizeof(TankStatus), &len);
- if(err != noErr) {
- ErrMsgCode("\p Failed: AESynchTank keyTankStatus typeTankStatus",err);
- }
- }
-
- /* if we got the status of the tank ok, then we call the Synch. Tank function
- (See TankSprites.c) to make sure the tank is in the right place doin the right
- thing.
-
- After that, the remote missiles are updated by passing the whole applevent
- to MissileSprites.c via ProcessMissilePositions.
- */
- if(err == noErr) {
- SynchronizeTank(gGame, &tStatus.position, tStatus.direction, tStatus.speed);
- err = ProcessMissilePositions(theAE);
- }
-
- /* eat the synch time errors */
- if(err == 1) err = noErr;
-
- return err;
- }
-
-
-
-
- Boolean TankSynchTask(xthing *xtp, spritePtr spr)
- /*
- This is a timed task managed by the xthing time manager.
- It sends the state of the tank and the state of the missiles
- to the remote mac.
-
- See the notes above about abusing and sending structures using AppleEvents.
-
- Good. Now that you have read that, I'll elaborate more.
- One of the key concepts with scriptable AppleEvent programs is to
- use tagged data so that any application can determine the format of
- parameters and send them along in a happy manner. This may require many
- calls to AEPutParamPtr and such, which may also cause the AppleEvent record
- to grow multiple times, and can cause a slow down for a real time application
- like this. So, to minimize this, I kind go against the grain of AppleEvents and
- just send a big block of data. The good design thing for me to have done, which I
- didn't, would have been to isolate the network code a little more, so that any layer could
- be plugged in.
- */
- {
- OSErr err = noErr;
- AppleEvent tankSynchEvent;
- AppleEvent reply;
- tankInfoRec *tInfo;
- Boolean disposeNeeded = false;
- long tickTime;
- TankStatus tStatus;
-
- if(gLastReturnTime != gLocalTime) return true;
-
- tInfo = (tankInfoRec*)spr->refCon;
-
- err = AECreateAppleEvent(kZAMEventClass, kTankSynchID, &gGame->oppAddr,
- kTimeID, gGame->gameID, &tankSynchEvent);
- if(err != noErr) {
- ErrMsgCode("\p Failure: FireNetworkMissile AECreateAppleEvent",err);
- }
-
-
- /*
- This is where the time stamp is incremented, and placed into the appleevnet.
- This is sent back in the reply event */
-
- if(err == noErr) {
- disposeNeeded = true;
- gLocalTime++;
- err = AEPutParamPtr(&tankSynchEvent, keySynchTime, typeLongInteger,
- &gLocalTime, sizeof(long));
- if(err != noErr) {
- ErrMsgCode("\p Failure: AEPutParamPtr",err);
- }
- }
-
- /* load up the tank info and stuff it into the event */
- tStatus.position = spr->loc;
- tStatus.direction = tInfo->dir;
- tStatus.speed = tInfo->speed;
-
- if(err == noErr) {
- err = AEPutParamPtr(&tankSynchEvent, keyTankStatus, typeTankStatus,
- &tStatus, sizeof(TankStatus));
- if(err != noErr) {
- ErrMsgCode("\p Failure: AEPutParamPtr keyTankStatus, typeTankStatus",err);
- }
- }
-
- /* getting a little more structured here, I call out to add the missile information */
- /* to the event. CAn you tell that I did the tank stuff first, and missile stuff
- later? */
- if(err == noErr) {
- err = AppendMissilePositions(&tankSynchEvent);
- if(err != noErr) {
- ErrMsgCode("\pError appending missile positions.",err);
- }
- }
-
- if(err == noErr) {
- err = AESend(&tankSynchEvent, &reply, kAEQueueReply + kAECanInteract,
- kAEHighPriority, kNoTimeOut, nil, nil);
- if(err != noErr) {
- ErrMsgCode("\p Failure: AESend",err);
- }
- }
-
- if(disposeNeeded) {
- AEDisposeDesc(&tankSynchEvent);
- }
-
- return true;
- }
-
-